<?php
	/**
	  * @brief Clase abstracta para convertir ficheros DBF a MySQL
	  * ----------------------------------------------------------------------------
	  * Distribuido bajo la licencia GNU General Public License V2
	  * ----------------------------------------------------------------------------
	  * @author Hermann D. Schimpf <hschimpf@gschimpf.com>
	  * @date 2009-09-15
	  * @version 0.3
  	**/

	abstract class DBF2MySQL {
		protected $archivoDBF	= "";
		protected $nombre		= "";
		protected $tamano		= 0;
		protected $cantidadCampos		= 0;
		protected $cantidadRegistros	= 0;
		protected $titulosCampos	= Array();
		protected $salidaSQL		= Array();
		protected $tiempoInicio		= 0;

		protected function __construct($ficheroDBF) {
			/* almacenamos el nombre de la tabla */
			$this->archivoDBF = $ficheroDBF;

			/* obtenemos el nombre del fichero */
			$this->nombre = $this->getNombreOnly();

			/* obtenemos el tamano del fichero */
			$this->tamano = filesize($this->archivoDBF);

			/* tomamos el tiempo de inicio */
			$this->tiempoInicio = $this->getFullTime();
		}

		/*abstract protected function mostrarSQL($archivo);/* {
			// creamos un objeto del tipo dbfFile
			$dbf2sql = new dbf2mysql($archivo);
			// si se pudo convertir mostramos el resultado
			if ($dbf2sql->convert())
				$dbf2sql->showSQL(True);
		}*/

		/*abstract protected function enviarSQL($archivo);/* {
			// creamos un objeto del tipo dbfFile
			$dbf2sql = new dbf2mysql($archivo);
			// si se pudo convertir enviamos el archivo
			if ($dbf2sql->convert())
				$dbf2sql->sendSQL();
		}*/

		/*public static function guardarSQL($archivo) {
			// creamos un objeto del tipo dbfFile
			$dbf2sql = new dbf2mysql($archivo);
			// si se pudo convertir retornamos el resultado
			if ($dbf2sql->convert())
				$dbf2sql->saveToFile();
		}*/

		/*public static function getSQL($archivo) {
			// creamos un objeto del tipo dbfFile
			$dbf2sql = new dbf2mysql($archivo);
			// si se pudo convertir retornamos el resultado
			if ($dbf2sql->convert())
				return $dbf2sql->salidaSQL;
		}*/

		protected function getName() {
			return $this->nombre;
		}

		protected function getNombreOnly() {
			/* obtenemos el nombre del fichero eliminando las carpetas anteriores al mismo */
			$nombreCompleto = explode("/", $this->archivoDBF);
			$nombreCompleto = $nombreCompleto[count($nombreCompleto) - 1];
			/* eliminamos la extension del nombre. ej: fichero.dbf -> ficherodbf */
			$nombreCompleto = explode(".", $nombreCompleto);
			$retorno = "";
			foreach($nombreCompleto AS $parteNombre)
				$retorno .= $parteNombre;
			return $retorno;
		}

		protected function getFullTime() {
			$timeInicio = explode(" ",microtime());
			return $timeInicio[1] + $timeInicio[0];
		}

		protected function sendSQL() {
			/* esta funcion envia el fichero para descargar utilizando las cabeceras HEADER */
			/* envio el nombre del fichero */
			header("Content-Disposition: attachment; filename=" . $this->getName() . ".sql");
			/* envio el tipo de archivo que estoy enviando */
			header("Content-Type: application/octet-stream");
			/* enviamos el archivo */
			$this->showSQL();
		}

		protected function showSQL($enHTML = false) {
			/*
				esta funcion muestra la consulta SQL final
				recorremos la salida y enviamos linea por linea
			*/
			foreach($this->salidaSQL AS $lineaSQL) {
				if ($enHTML)
					echo "<BR/>\n$lineaSQL";
				else
					echo "$lineaSQL\n";
			}
		}

		protected function saveToFile() {
			$this->fileName = $this->nombre . ".sql";
			/* si el archivo ya existe se elimina */
			if (is_file($this->fileName)) {
				echo "Ya existe otro fichero con el nombre '" . $this->getName() . "' en el directorio actual<BR />";
				exit;
			}
			/* abrimos el archivo */
			if ($file = @fopen($this->fileName,"w")) {
				foreach($this->salidaSQL as $linea)
					fputs($file, "$linea\n");
				fclose($file);
				echo "El archivo se almaceno en el directorio actual con el nombre '". $this->getName() ."'<BR />";
			} else
				echo "No se puede escribir en el directorio<BR />";
		}

		protected function convert() {
			/* verificamos que se posea la libreria para trabajar con ficheros dBase */
			if ($this->dBaseOk()) {
				/* verificamos que el archivo exista */
				if (is_file($this->archivoDBF) AND is_readable($this->archivoDBF)) {
					/* Si todo esta Ok abrimos el fichero para trabajar sobre el */
					$this->archivoDBF		= dbase_open($this->archivoDBF,0);
					/* obtenemos la cantidad de campos */
					$this->cantidadCampos	= dbase_numfields($this->archivoDBF);
					/* obtenemos la cantidad de registros */
					$this->cantidadRegistros= dbase_numrecords($this->archivoDBF);
					/* obtenemos los titulos de los campos */
					$this->titulosCampos	= dbase_get_header_info($this->archivoDBF);
					/* convertimos el fichero DBF a SQL */
					return $this->convertir2sql();
				} else {
					echo "El fichero '" . $this->getName() . "' no existe o no tiene permisos de lectura<BR />";
					return False;
				}
			} else {
				echo "Se necesita la libreria 'dbase' para poder convertir ficheros DBF<BR />";
				return False;
			}
		}

		protected function dBaseOk() {
			/* con esta funcion verifico si existe la libreria necesaria para trabajar con ficheros DBF */
			/* obtengo las librerias compiladas en PHP */
			$utilidades = get_loaded_extensions();
			/* recorro las librerias para verificar si existe dbase */
			foreach (get_loaded_extensions() AS $libreria)
				if (strtolower($libreria) == "dbase")
					/* si existe retorno true */
					return True;
			/* si llegue aqui es porque no existe. retorno false */
			return False;
		}

		protected function convertir2sql() {
			/* creamos la cabecera del archivo SQL */
			$this->crearCabecera();

			/* creamos la tabla */
			if (!$this->crearTabla())
				/* si se produzco un error retornamos false */
				return False;

			/* volvamos la tabla */
			if (!$this->crearRegistros())
				/* si se produzco un error retornamos false */
				return False;

			/* cerramos el fichero DBF */
			if (!$this->cerrarDBF())
				/* si se produzco un error retornamos false */
				return False;

			/* creamos el footer del archivo */
			$this->crearFooter();

			/* si llegamos aqui todo fue bien */
			return True;
		}

		protected function crearCabecera() {
			$this->agregar("--");
			$this->agregar("-- HDS Converter 0.2 (2009-08-20)");
			$this->agregar("-- Contact to: gschimpf.com");
			$this->agregar("--");
			$this->agregar("");
			$this->agregar("/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;");
			$this->agregar("/*!40103 SET TIME_ZONE='+00:00' */;");
			$this->agregar("/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;");
			$this->agregar("/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;");
			$this->agregar("/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;");
			$this->agregar("/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;");
			$this->agregar("");
		}

		protected function crearTabla() {
			/*
				creamos la cabecera con el nombre y el primer campo que sera la palabla 'cod' + el nombre de la tabla
				ej: 'codNombretabla'
			*/
			$nombreTabla = $this->getName();
			$codPrimario = "cod" . ucfirst($this->getName());
			/* creamos la linea inicial del CREATE TABLE */
			$this->cabeceraTabla = "CREATE TABLE IF NOT EXISTS $nombreTabla (`$codPrimario` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY";
			/* verificamos que alla tomado valores */
			if ($nombreTabla == "" OR $codPrimario == "cod") {
				echo "La cabecera no se creo correctamente<BR />";
				return False;
			}
			/* recorremos los campos tomando el nombre de cada uno y su tipo */
			for ($i = 0; $i < $this->cantidadCampos; $i++) {
				/* obtenemos los datos campo */
				$campo = $this->titulosCampos[$i];
				/* obtenemos el nombre desde los datos campo */
				$tituloCampo = strtolower($campo['name']);
				/* obtenemos el tipo desde los datos campo */
				$tipoCampo = $this->getTipoCampo($campo['type']);
				/* verificamos que alla tomado valores */
				if ($tituloCampo == "" OR $tipoCampo == "") {
					echo "Uno de los campos no se creo correctamente<BR />";
					return False;
				}
				/* armamos el tipo de campo */
				$this->cabeceraTabla .= ", `$tituloCampo` $tipoCampo NULL";
			}
			/* finalizamos la cabecera */
			$this->cabeceraTabla .= ");";
			/* cargamos la cabecera a la salida SQL y si se produzco un error retornamos false */
			$this->agregar($this->cabeceraTabla);
			/* si llegamos hasta aqui todo va Ok */
			return True;
		}

		protected function getTipoCampo($tipo) {
			switch($tipo) {
				case 'number':
					$tipo = 'int(11)';
					break;
				case 'date':
					$tipo = 'date';
					break;
				case 'character':
					$tipo = 'varchar(250)';
					break;
				case 'boolean':
					$tipo = 'binary(1)';
					break;
				case 'memo':
					$tipo = 'longtext';
					break;
			}
			return $tipo;
		}

		protected function crearRegistros() {
			/* agregamos una cabecera */
			$this->crearCabeceraVolcado();
			/* agregamos la linea para realizar el bloqueo de la tabla */
			$this->bloquearTabla();

			/* volvamos los registros */
			if (!$this->volcarRegistros())
				/* si se produzco un error retornamos false */
				return False;

			/* desbloqueamos de la tabla */
			$this->desbloquearTabla();

			/* si llegamos hasta aqui todo va Ok */
			return True;
		}

		protected function crearCabeceraVolcado() {
			/* ponemos una cabecera de la tabla */
			$this->agregar("");
			$this->agregar("--");
			$this->agregar("-- Volcando datos para la tabla " . $this->getName());
			$this->agregar("--");
			$this->agregar("");
		}

		abstract protected function volcarRegistros();/* {
			// recorremos los registros
			for ($i = 1; $i <= $this->cantidadRegistros; $i++) {
				// creamos la linea INSERT.
				$this->crearLineaInsert();

				// obtengo el valor del registro
				if (!$this->obtenerRegistro($i))
					// si se produzco un error retornamos false
					return False;

				// por cada registro tenemos que recorrer todos los campos
				for ($j = 0; $j < $this->cantidadCampos; $j++) {
 					// agregamos el campo a la linea INSERT
 					$this->agregarItem($j);
				}
				// siempre queda una cadena ', ' de mas. La eliminamos
				$this->eliminarSobra();
				// finalizamos la linea INSERT
				$this->finalizarInsert();
				// agregamos la linea INSERT a la salida
				$this->agregar($this->lineaInsert);
			}
			// si llegamos hasta aqui todo va Ok
			return True;
		}*/

		protected function crearLineaInsert() {
			/* Usamos INSERT IGNORE para evitar problemas de registros repetidos */
			$this->lineaInsert = "INSERT IGNORE INTO `" . $this->getName() . "` VALUES (Null,";
		}

		protected function obtenerRegistro($lugar) {
			$this->registro = dbase_get_record($this->archivoDBF,$lugar);
			if (!$this->registro) {
				/* si se produzco un error retornamos false */
				echo "No se pudo obtener el registro<BR />";
				return False;
			}
			return True;
		}

		protected function removerCaracteres($lugar) {
			switch ($this->titulosCampos[$lugar]['type']) {
				case "character":
				case "memo":
					/* si el tipo de dato es alfanumerico convertimos los posibles caracteres que afecten */
					/* convertimos los apostrofes, acentos y barras invertidas */
					$this->registro[$lugar] = str_replace("'","&apos;",$this->registro[$lugar]);
					$this->registro[$lugar] = str_replace("\"","&quot;",$this->registro[$lugar]);
					$this->registro[$lugar] = str_replace("`","&#096;",$this->registro[$lugar]);
					$this->registro[$lugar] = str_replace("´","&acute;",$this->registro[$lugar]);
					$this->registro[$lugar] = str_replace("\\","&#092;",$this->registro[$lugar]);
					/* convertimos los caracteres raros a caracteres HTML */
					$this->registro[$lugar] = strtr($this->registro[$lugar], get_html_translation_table(HTML_ENTITIES));
					/* transformamos los enter en <BR> */
					$this->registro[$lugar] = nl2br($this->registro[$lugar]);
					/* eliminamos los espacios en blanco de mas */
					$this->registro[$lugar] = trim($this->registro[$lugar]);
					break;
				case "date":
					/* si el tipo de dato es fecha le damos formato para MySQL */
					return $this->agruparFecha($lugar);
					break;
			}
		}

		protected function agregarItem($lugar) {
			/* purgamos el valor a agregar */
			$this->removerCaracteres($lugar);
			/* agregamos el item a la cadena INSERT */
			switch ($this->titulosCampos[$lugar]['type']) {
				case "character":
				case "memo":
				case "date":
					/* si el tipo de dato es alfanumerico lo almacenamos entre comillas '' */
					$this->lineaInsert .= "'" . $this->registro[$lugar] . "', ";
					break;
				default:
					/* si el tipo de registro no es alfanumerico almacenamos el valor en bruto */
					$this->lineaInsert .= $this->registro[$lugar] . ", ";
					break;
			}
		}

		protected function agruparFecha($lugar) {
			/* separamos el ano mes y dia de la fecha para convertirlo en MySQL. ej: '20090201' -> '2009-02-01' */
			$ano = substr($this->registro[$lugar],0,4);
			$mes = substr($this->registro[$lugar],4,2);
			$dia = substr($this->registro[$lugar],6,2);

			/* armamos la cadena de fecha en MySQL */
			$validar = "$ano-$mes-$dia";

			/* eliminamos los espacios y el caracter '-' */
			$validar = str_replace("-","",$this->registro[$lugar]);
			$validar = str_replace(" ","",$validar);

			/* verificamos si tiene valor */
			if (strlen($validar) == 8) {
				/* si tiene un valor verificamos si es una fecha correcta */
				if (!checkdate($mes,$dia,$ano)) {
					/* si no es una fecha correcta mostramos un error y retornamos false */
					echo "La fecha del registro $lugar no es valida. Valor: '" . $this->registro[$lugar] . "'<BR />";
					return False;
				} else {
					/* si es correcta  retornamos true */
					$this->registro[$lugar] = "$ano-$mes-$dia";
					return True;
				}
			} elseif (strlen($validar) == 0) {
				/* si no tiene valor retornamos true */
				$this->registro[$lugar] = "";
				return True;
			} else {
				/* si tiene algun valor que no es de 8 digitos es una fecha incorrecta */
				echo "La fecha del registro $lugar no es valida. Valor: '" . $this->registro[$lugar] . "'<BR />";
				return False;
			}
		}

		protected function eliminarSobra() {
			/* eliminamos la coma y espacio ', ' sobrante que queda en la cadena */
			$this->lineaInsert = substr($this->lineaInsert,0,(strlen($this->lineaInsert) - 2));
		}

		protected function finalizarInsert() {
			/* siempre queda una cadena ', ' de mas. La eliminamos antes de cerrar el INSERT */
			$this->eliminarSobra();
			/* cerramos el parentesis de la cadena INSERT */
			$this->lineaInsert .= ");";
		}

		protected function cerrarDBF() {
			/* cerramos la tabla */
			if (!dbase_close($this->archivoDBF))
				return False;
			return True;
		}

		protected function bloquearTabla() {
			/* agregamos una linea para que se realize el bloqueo de la tabla */
			$this->agregar("LOCK TABLES `" . $this->getName() . "` WRITE;");
		}

		protected function desbloquearTabla() {
			/* agregamos una linea para desbloquear la tabla */
			$this->agregar("UNLOCK TABLES;");
		}

		protected function crearFooter() {
			$this->agregar("/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;");
			$this->agregar("");
			$this->agregar("/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;");
			$this->agregar("/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;");
			$this->agregar("/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;");
			$this->agregar("/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;");
			$this->agregar("");
			$this->agregar("-- Conversion finalizada. " . date("Y-m-d H:i:s"));
			$this->agregar("-- Duracion de la conversion: " . $this->tiempoTotal());
		}

		protected function tiempoTotal() {
			$this->tiempoInicio = $this->getFullTime() - $this->tiempoInicio;
			return number_format($this->tiempoInicio,4,",",".") . " segundos";
		}

		protected function agregar($linea) {
			/* agregamos una linea a la salida SQL */
			$this->salidaSQL[] = $linea;
		}
	}
?>